package com.acme.bankapp.service;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.StringTokenizer;

import com.acme.bankapp.domain.Bank;
import com.acme.bankapp.domain.Client;
import com.acme.bankapp.domain.Gender;
import com.acme.bankapp.domain.accounts.AccountType;
import com.acme.bankapp.exceptions.ClientExistsException;

/**
 * Performs (de)serialization and loads Bank from feed file.
 *
 */
public class BankIOService {
	public Bank bank;

	/**
	 * Instantiates service with specified bank.
	 */
	public BankIOService(Bank bank) {
		this.bank = bank;
	}

	/**
	 * Deserializes Bank from specified path.
	 * @param path file path
	 * @return Bank object
	 */
	public Bank readBank(String path) {
		try (	FileInputStream fis = new FileInputStream(path);
				BufferedInputStream buf = new BufferedInputStream(fis);
				ObjectInputStream ois = new ObjectInputStream(buf);
				) {

			return (Bank) ois.readObject();
		} catch (IOException e) {
			System.out.println("--- Error: Opening file \"" + path + "\"  failed.");
		} catch (ClassNotFoundException ce) {
			System.out.println("--- Error: Class not found.");
		}

		return null;		
	}

	/**
	 * Serializes Bank to specified path.
	 * @param bank object
	 * @param path file path
	 */
	public void writeBank(String path) {
		try (	FileOutputStream fos = new FileOutputStream(path);
				BufferedOutputStream buf = new BufferedOutputStream(fos);
				ObjectOutputStream oos = new ObjectOutputStream(fos);
				) {
			
			oos.writeObject(bank);

		} catch (IOException e) {
			System.out.println("--- Error: Opening file \"" + path + "\"  failed.");
		}

	}

	/**
	 * Loads Bank from Feed file with specified path.
	 * @param path file path
	 * @return Bank object
	 */
	public Bank loadBankFromFeedFile(String path) {
		try (	FileInputStream fis = new FileInputStream(path);
				InputStreamReader isr = new InputStreamReader(fis);
				BufferedReader buf = new BufferedReader(isr);
				) {

			String s = "";
			while (((s = buf.readLine()) != null) && (addClientByFeed(bank, s)));
			return bank;

		} catch (IOException e) {
			System.out.println("--- Error: Opening file \"" + path + "\"  failed.");
		}

		return null;
	}

	//extracts Value from string of format "Key=Value"
	private String getFeedKeyValue(String token) {
		return token.substring(token.indexOf('=') + 1);
	}

	//is used by addClientByFeed()
	public Client createClientFromStringTokenizer(StringTokenizer tok, String delim) {
		//extract field values from Tokenizer
		AccountType accountType = AccountType.valueOf (getFeedKeyValue(tok.nextToken(delim)).charAt(0));
		double balance 			= Double.parseDouble  (getFeedKeyValue(tok.nextToken(delim)));
		double overdraft    	= (accountType == AccountType.CHECKING) 
								  ? Double.parseDouble(getFeedKeyValue(tok.nextToken(delim))) 
								  : 0.;
		String name   			= getFeedKeyValue(tok.nextToken(delim));
		Gender gender 			= Gender.valueOf (getFeedKeyValue(tok.nextToken(delim)).charAt(0));
		
		Client client = new Client(name, gender, AccountService.createAccountByType(accountType, balance, overdraft));
		
		return client;
	}

	/**
	 * Adds client to bank from specified feed-formatted string.
	 * @param bank TODO
	 * @param feed Example: "accounttype=c;balance=10000;overdraft=0;name=Daisy;gender=f;"
	 */
	public boolean addClientByFeed(Bank bank, String feed) {
		try {
			bank.addClient(createClientFromStringTokenizer(new StringTokenizer(feed), ";"));
		} catch (ClientExistsException e) {
			System.out.println(e.getMessage());
			return false;
		}

		return true;
	}

	/**
	 * Displays a list of client infos.
	 * @param bank the bank.
	 */
	public static void showAllAccounts(Bank bank) {
		System.out.println("\n--- List of all accounts for all clients:");
		for (Client client : bank.getClients()) {
			System.out.println(client);
		}
		System.out.println();
	}

	/**
	 * Returns a formatted string - a list of client infos.
	 * @param bank the bank.
	 * @return 
	 */
	public static String getAllAccountsString(Bank bank) {
		StringBuffer buf = new StringBuffer("");
		buf.append("--- List of all accounts for all clients:\n");
		for (Client client : bank.getClients()) {
			buf.append(client).append("\n");
		}
		return buf.append("\n").toString();
	}

	
	/**
	 * Displays a list of clients' names.
	 */
	public static void showAllClientNames(Bank bank) {
		System.out.println("\n--- List of all client names:");
		for (Client client : bank.getClients()) {
			System.out.println(client.getName() + ", " + client.getGender());
		}
		System.out.println();
	}

	/**
	 * Displays specified Client object's info in single-line form.
	 * @param client - desired Client object.
	 */
	public static void showClient(Client client) {
		System.out.println(client);
	}

	/**
	 * Displays specified Client object's info in multiline form if needed.
	 * @param client - desired Client object.
	 * @param verbose - if true, then multiline.
	 */
	public static void showClient(Client client, boolean verbose) {
		if (verbose) {
			System.out.println("\n--- Client info:");
			showClient(client);
			System.out.println();
		}
	}

	/**
	 * Finds Client by name and displays its info in single-line form.
	 * @param clientName - Name of the client.
	 */
	public static void showClient(String clientName, Bank bank) {
		showClient(bank.getClientByName(clientName));
	}

	/**
	 * Finds Client by name and displays its info in multi-line form if needed.
	 * @param clientName - Name of the client.
	 * @param verbose - If true, then multiline.
	 */
	public static void showClient(String clientName, Bank bank, boolean verbose) {
		showClient(bank.getClientByName(clientName), verbose);
	}
}
